#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h>
#include <string.h>
#include <sys/mman.h>

int main(void) 
{
    int ret = -1;
    int fd = open("/dev/video0",O_RDWR);
    if (fd < 0) {
        perror("open camera failed\n");
        return -1;
    }

    //获取摄像头支持格式 ioctl(文件描述符,命令，与命令对应的结构体)
    struct v4l2_fmtdesc fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集

    int i = 0;
    while(1) {
        fmt.index = i;
        i++;
        ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmt);
        if (ret < 0) {
            perror("get fmt failed");
            break;
        }
        printf("index=%d\n",fmt.index);
        printf("flags=%d\n",fmt.flags);
        printf("discription=%s\n",fmt.description);
        unsigned char *p = (unsigned char *)&fmt.pixelformat;
        printf("pixelformat=0x%X\n", fmt.pixelformat);
        printf("reserved=%d\n",fmt.reserved[0]);
    }

    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //摄像头采集
    vfmt.fmt.pix.width = 640; //设置摄像头采集参数，不可以任意设置
    vfmt.fmt.pix.height = 480;
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YYUV; //设置视频采集格式 ，根据上一步测得，注意格式有yyuv和yuyv不要搞混
    
    ret = ioctl(fd,VIDIOC_S_FMT,&vfmt); //设置格式命令
    if (ret < 0)
    {
        perror("设置格式失败1");
    }
    printf("set pix success\n");

    memset(&vfmt,0,sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd,VIDIOC_G_FMT,&vfmt);
    if (ret < 0)
    {
        perror("设置格式失败2");
    }
    printf("set type success\n");

    if(vfmt.fmt.pix.width == 640 && vfmt.fmt.pix.height == 480 && vfmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YYUV)
    {
        printf("设置成功！\n");
    }else
    {
        printf("设置失败3\n");
    }
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4; //申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP;  //映射方式

    ret = ioctl(fd,VIDIOC_REQBUFS,&reqbuffer);
    if (ret < 0)
    {
        perror("申请空间失败");
    }
    printf("alloc space success\n");
    //映射
    unsigned char *mptr[4];//保存映射后用户空间的首地址
    struct v4l2_buffer mapbuffer;
    unsigned int size[4];
    //初始化type和index
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    for(int i = 0; i <4;i++) {  //使用for对四个申请的空间进行轮询
        mapbuffer.index = i;
        ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer); //从内核空间中查询一个空间作映射
        if (ret < 0)
        {
            perror("查询内核空间失败");
        }
        //映射到用户空间
        mptr[i] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
        //查询后通知内核已经放回
        ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
        if (ret < 0)
        {
            perror("放回失败");
        }
    }
    //开始采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd,VIDIOC_STREAMON,&type); 
    if (ret < 0)
    {
        perror("开启失败");
    }
    printf("start capture\n");
    //从队列中提取一帧数据
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //每个结构体都需要设置type为这个参赛要记住
    ret = ioctl(fd,VIDIOC_DQBUF,&readbuffer); 
    if (ret < 0)
    {
        perror("读取数据失败");
    }
    printf("read buffer\n");
    
    FILE *file=fopen("my.jpeg", "w+");  //打开一个文件
    fwrite( mptr[readbuffer.index],readbuffer.length,1,file);//写入文件
    fclose(file);    //写入完成，关闭文件

    //通知内核使用完毕
   ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
   if(ret < 0)
	{
		perror("放回队列失败");
	}
   printf("qbuf\n");
	
    //停止采集
    ret = ioctl(fd,VIDIOC_STREAMOFF,&type);
    printf("stop capture\n");
    
	//释放映射
    for(int i=0; i<4; i++){
        printf("i = %d\n", i);
        munmap(mptr[i], size[i]);
    } 
    close(fd);
    return 0;
}

